package com.qhs.framework.utils;

import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Repository;

/**
 * 主键生成
 */
@Scope("singleton")
@Repository("idWork")
public class IDWorker {

    private Long HORIZON_MILLI = 1201303131230L;
    private final static Integer SEQUENCE_BITS = 12;
    private final static Integer WORK_ID_BITS = 5;
    private final static Integer DATA_CENTER_BITS = 5;
    private final static Integer MILLI_SECOND_BITS = 41;
    private final static Integer MAX_SEQUENCE = -1 ^ (-1 << SEQUENCE_BITS);
    private final static Integer MAX_WORK_ID = -1 ^ (-1 << WORK_ID_BITS);
    private final static Integer MAX_DATA_CENTER_ID = -1 ^ (-1 << DATA_CENTER_BITS);
    private final static Long MAX_MILLISECONDS = -1L ^ (-1L << MILLI_SECOND_BITS);

    private final static Integer MILLI_SHIFT = DATA_CENTER_BITS + WORK_ID_BITS + SEQUENCE_BITS;
    private final static Integer DATA_CENTER_SHIFT = WORK_ID_BITS + SEQUENCE_BITS;
    private final static Integer MACHINE_ID_SHIFT = SEQUENCE_BITS;

    private int dataCenterId;
    private int machineId;

    private long sequence = 0;
    private long lastTimestamp = 0;

    private final static int default_dataCenterId =16;
    private final static int default_machineId =15;

    public IDWorker() {
        this(default_dataCenterId, default_machineId);
    }

    public IDWorker(int decenterId, int machineId) {
        if (decenterId < 0 || decenterId > 99) {
            throw new RuntimeException("the decenterId invalid");
        }

        if (machineId < 0 || machineId > 99) {
            throw new RuntimeException("the machineId invalid");
        }

        this.dataCenterId = decenterId;
        this.machineId = machineId;
    }

    public synchronized long generateId() {
        long milliseconds = System.currentTimeMillis();
        if (this.lastTimestamp == milliseconds) {
            this.sequence = (this.sequence + 1) & MAX_SEQUENCE;
            if (this.sequence == 0) {
                milliseconds = tilNextMillis(this.lastTimestamp);
            }
        } else if (milliseconds < this.lastTimestamp) {
            throw new RuntimeException("clock wrong");
        } else {
            this.sequence = 0;
        }
        this.lastTimestamp = milliseconds;
        if (((milliseconds - HORIZON_MILLI) & MAX_MILLISECONDS) == 0) {
            throw new RuntimeException("time is too large");
        }
        long nextId = ((milliseconds - HORIZON_MILLI) << MILLI_SHIFT) + (this.dataCenterId << DATA_CENTER_SHIFT) + (this.machineId << MACHINE_ID_SHIFT) + this.sequence;
        return nextId;
    }

    private long tilNextMillis(final long lastTimestamp) {
        long timestamp = System.currentTimeMillis();
        while (timestamp <= lastTimestamp) {
            timestamp = System.currentTimeMillis();
        }
        return timestamp;
    }


}
